package feedgraph

import (
	"strings"
	"log"
	"path"
	"net/url"
	"net/http"
	"encoding/json"
	"io/ioutil"
	"bytes"
	"io"
	"errors"
	"code.google.com/p/go-uuid/uuid"
)

const BaseURL = "http://api.feedgraph.com"

type Iterator chan interface{}

type LinkNotification struct {
	Source string
	RelationType string
	Sink string
}

type StoreNotification struct {
	Source string 
	Object interface{}
}

// for when all you care about is a node's Name
type Named struct {
	Name string
}

type edge struct {
	dest string
	obj interface{}
}

type EdgeIterator chan edge

type NodeIterator struct {
	raw Iterator
}

// Message referencing a node
type NodeMessage struct {
	Node string
}

func clean(s string) string {

	s = strings.Replace(s, " ", "+", -1)

	return path.Clean("/" + s)
}

func (iter NodeIterator) Next(obj interface{}) (ok bool) {

	var msg NodeMessage

	ok = iter.raw.Next(&msg)

	if ok {

		err := Retrieve(msg.Node, obj)

		if err != nil {
			panic(err)
		}
	}

	return
}

func (iter Iterator) Next(obj interface{}) bool {

	item, ok := <-iter

	if !ok {
		return false
	}

	text, err := json.Marshal(item)

	if err != nil {
		panic(err)
	}

	err = json.Unmarshal(text, &obj)

	if err != nil {
		panic(err)
	}

	return true
}

func (iter Iterator) Close() {

	close(iter)
}

func (iter EdgeIterator) Next(obj interface{}) (dest string, ok bool) {

	edge, ok := <-iter

	if !ok {
		return 
	}

	dest = edge.dest

	text, err := json.Marshal(edge.obj)

	if err != nil {
		panic(err)
	}

	err = json.Unmarshal(text, &obj)

	if err != nil {
		panic(err)
	}

	return 
}

// Simple utility function.  
// Create a new node with a unique path (UUID).
// If obj is not nil, NewNode stores the obj at the newly created node.
// Returns path to new node.
func NewNode(obj interface{}) (path string, err error) {

	path = uuid.New()

	if obj != nil {
		err = Store(path, obj)
	}

	return
}

func Retrieve(path string, obj interface{}) error {

	uri := BaseURL + clean(path)

	resp, err := http.Get(uri)

	if err != nil {
		return err
	}

	data, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		return err
	}

	defer resp.Body.Close()	

	err = json.Unmarshal(data, obj)

	if err != nil {
		return err
	}

	return nil
}

func Edges(path, rel string) (iter EdgeIterator, err error) {

	uri := BaseURL + clean(path) + "?" + clean(rel)

	resp, err := http.Get(uri)

	if err != nil {
		return 
	}

	data, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		return
	}

	defer resp.Body.Close()	

	var edges map[string]interface{}

	err = json.Unmarshal(data, &edges)

	iter = make(EdgeIterator)

	go func () {

		for dest, obj := range edges {
			iter <- edge{dest, obj}
		}

		close (iter)
	} ()

	return 
}



// redundently marshal and unmarshal so we can detect json errors locally
func StoreStream(node string, r io.Reader) error {

	var obj interface{}

	text, err := ioutil.ReadAll(r)

	if err != nil {
		return err
	}

	err = json.Unmarshal(text, &obj)

	if err != nil {
		return err
	}

	return Store(node, obj)
}

func Store(path string, obj interface{}) error {

	data, err := json.Marshal(obj)

	if err != nil {
		return err
	}

	uri := BaseURL + clean(path)

	req, err := http.NewRequest("PUT", uri, bytes.NewBuffer(data))
	
	if err != nil {
		return err
	}

	resp, err := http.DefaultClient.Do(req)

	if err != nil {
		return err
	}

	if resp.StatusCode != 200 {
		return errors.New(resp.Status)
	}

	return nil
}

func Attach(node string, r io.Reader) error {

	uri := BaseURL + clean(node) + ".html"

	req, err := http.NewRequest("PUT", uri, r)
	
	if err != nil {
		return err
	}

	resp, err := http.DefaultClient.Do(req)

	if err != nil {
		return err
	}

	if resp.StatusCode != 200 {
		return errors.New(resp.Status)
	}

	return nil
}

func Link(path, rel, sink string) error {

	rel = url.QueryEscape(rel)
	sink = url.QueryEscape(clean(sink))

	req, err := http.NewRequest("PUT", BaseURL + clean(path) + "?" + rel + "&" + sink, nil)
	
	if err != nil {
		return err
	}

	resp, err := http.DefaultClient.Do(req)

	if err != nil {
		return err
	}

	if resp.StatusCode != 200 {
		return errors.New(resp.Status)
	}

	return nil
}

func RenderHTML(path, templ string) (io.ReadCloser, error) {

	resp, err := http.Get(BaseURL + path + "?t=" + templ)

	if err != nil {
		return nil, err
	}

	return resp.Body, nil
}

func Publish(path string, msg interface{}) error {

	data, err := json.Marshal(msg)

	if err != nil {
		return err
	}

	uri := BaseURL + clean(path)

	req, err := http.NewRequest("POST", uri, bytes.NewBuffer(data))
	
	if err != nil {
		return err
	}

	resp, err := http.DefaultClient.Do(req)

	if err != nil {
		return err
	}

	if resp.StatusCode != 200 {
		return errors.New(resp.Status)
	}

	return nil
}

// Simple utility function.
// Publish a message referencing a node.
// Subscribers will be notified with a {Node:path} message.
// This can be used with NewNode to alert subscribers of newly created nodes.
func PublishNode(mailbox, node string) (err error) {

	err = Publish(mailbox, NodeMessage{node})

	return
}

func Listen(path string) (Iterator, error) {

	iter := make(Iterator)

	go listen(path, iter)

	return iter, nil
}

func listen(path string, ch chan interface{}) error {

	uri := BaseURL + clean(path) + ".mailbox"

	resp, err := http.Get(uri)

	if err != nil {
		return err
	}

	data, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		return err
	}

	defer resp.Body.Close()

	var msgs []interface{}

	err = json.Unmarshal(data, &msgs)

	if err != nil {
		return err
	}

	for _, msg := range msgs {
		ch <- msg
	}

	return listen(path, ch)
}

func Subscribe(path string) (iter Iterator, err error) {

	sub := uuid.New()

	err = Link(path, "/subscribers", sub)

	if err != nil {
		log.Println(err)
		return nil, err
	}

	return Listen(sub)
}

// Utility function.
// Subscribe to a mailbox in which the messages are reference nodes.
func SubscribeNodes(mailbox string) (iter NodeIterator, err error) {

	rawiter, err := Subscribe(mailbox)

	if err != nil {
		return
	}

	iter = NodeIterator{rawiter}

	return
}

// Utility function.
// Subsribe to a mailbox in which messages reference nodes.
// Automatically retrieve and render the nodes with the given template node.
// The resulting HTML is sent thru the returned channel.
func SubscribeRenderedNodes(mailbox, templ string) (htmlchan chan []byte, err error) {

	iter, err := Subscribe(mailbox)

	htmlchan = make(chan []byte)

	go func () {

		var msg NodeMessage

		defer iter.Close()

		for iter.Next(&msg) {

			r, err := RenderHTML(msg.Node, templ)

			if err != nil {
				continue
			}

			defer r.Close()

			text, err := ioutil.ReadAll(r)

			if err != nil {
				continue
			}

			htmlchan <- text
		}
	} ()

	return
}

                
